package cn.slipi.core.plugin.pre;

import cn.slipi.common.constants.Constants;
import cn.slipi.common.entity.CsmGrant;
import cn.slipi.common.entity.RequestLog;
import cn.slipi.common.enums.PluginDetailEnum;
import cn.slipi.common.rule.custom.entity.MatchCondition;
import cn.slipi.common.utils.Md5Util;
import cn.slipi.core.cache.ApiManager;
import cn.slipi.core.plugin.AbstractPlugin;
import cn.slipi.core.plugin.IPluginChain;
import com.google.common.base.Joiner;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Comparator;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * SignPlugin
 */
public class SignPlugin extends AbstractPlugin {

    private static final Logger LOGGER = LoggerFactory.getLogger(SignPlugin.class);

    private final ApiManager apiManager;

    public SignPlugin(final ApiManager apiManager) {
        super(apiManager);
        this.apiManager = apiManager;
    }


    @Override
    protected Mono<Void> doExecute(ServerWebExchange exchange, IPluginChain chain) {
        RequestLog requestLog = (RequestLog) exchange.getAttributes().get(Constants.REQUEST_LOG);

        MatchCondition requestCondition = (MatchCondition) exchange.getAttributes().get(Constants.GATEWAY_HEADER);
        String requestAppKey = requestCondition.getAppKey();
        String requestApiCode = requestCondition.getApiCode();
        CsmGrant csmGrant = apiManager.getGrantByApiCodeAndAppKey(requestApiCode, requestAppKey);
        if (csmGrant == null) {
            requestLog.setSign("UNAUTHORIZED");
            exchange.getAttributes().put(Constants.REQUEST_LOG, requestLog);
            return this.response(HttpStatus.UNAUTHORIZED);
        }
        if (csmGrant.getEnable().equals(0)) {
            requestLog.setSign("UNAUTHORIZED");
            exchange.getAttributes().put(Constants.REQUEST_LOG, requestLog);
            return this.response(HttpStatus.UNAUTHORIZED);
        }
        if (StringUtils.isNoneBlank(requestAppKey) && StringUtils.equalsIgnoreCase(requestAppKey, csmGrant.getAppKey())) {
            if (!verifySign(requestCondition, csmGrant)) {
                requestLog.setSign("UNAUTHORIZED");
                exchange.getAttributes().put(Constants.REQUEST_LOG, requestLog);
                return this.response(HttpStatus.UNAUTHORIZED);
            }
        } else {
            requestLog.setSign("UNAUTHORIZED");
            exchange.getAttributes().put(Constants.REQUEST_LOG, requestLog);
            return this.response(HttpStatus.UNAUTHORIZED);
        }
        requestLog.setSign("ok");
        exchange.getAttributes().put(Constants.REQUEST_LOG, requestLog);
        return chain.execute(exchange);
    }

    /**
     * appKey 授权验证 （IP+API_CODE 使用 APP_SECRET加密）
     *
     * @param requestCondition
     * @param csmGrant
     * @return
     */
    private boolean verifySign(MatchCondition requestCondition, CsmGrant csmGrant) {
        String requestSignCode = requestCondition.getSignCode();
        LOGGER.info("服务调用者的验签为：{}", requestSignCode);
        String gateWayGrantSign = Md5Util.getMD5(mapToString(requestCondition), csmGrant.getAppSecret());
        LOGGER.info("服务网关授权验签为：{}", gateWayGrantSign);
        return StringUtils.equalsIgnoreCase(requestSignCode, gateWayGrantSign);
    }

    private String mapToString(MatchCondition requestCondition) {
        Map paramMap = Maps.newHashMapWithExpectedSize(3);
        buildParamMap(paramMap, requestCondition);
        return Stream.of(Constants.IP, Constants.APP_KEY, Constants.API_CODE)
                .sorted(Comparator.naturalOrder())
                .map(k -> Joiner.on("").join(k, paramMap.get(k).toString()))
                .collect(Collectors.joining())
                .trim();
    }

    private void buildParamMap(Map map, MatchCondition requestCondition) {
        map.put(Constants.IP, requestCondition.getIp());
        map.put(Constants.APP_KEY, requestCondition.getAppKey());
        map.put(Constants.API_CODE, requestCondition.getApiCode());
    }

    @Override
    public String getPluginPhase() {
        return PluginDetailEnum.SIGN.getPhase();
    }

    @Override
    public int getCode() {
        return PluginDetailEnum.SIGN.getCode();
    }

    @Override
    public String getName() {
        return PluginDetailEnum.SIGN.getName();
    }
}
